Skip to content

Implement tensor.isin #2098

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open

Conversation

ndgrigorian
Copy link
Collaborator

This PR proposes an implementation for isin, a function likely coming to a future array API specification, which leverages a similar kernel to the implementation of searchsorted

This implementation uses the searchsorted kernel to check if the value has a position in the array. If that position is the number of elements in the array, it is not a member. Otherwise, if arr[pos] == val for some array arr being searched for value val, then val is a member.

  • Have you provided a meaningful PR description?
  • Have you added a test, reproducer or referred to an issue with a reproducer?
  • Have you tested your changes locally for CPU and GPU devices?
  • Have you made sure that new changes do not introduce compiler warnings?
  • Have you checked performance impact of proposed changes?
  • Have you added documentation for your changes, if necessary?
  • Have you added your changes to the changelog?
  • If this PR is a work in progress, are you opening the PR as a draft?

Copy link

github-actions bot commented Jun 6, 2025

Copy link

github-actions bot commented Jun 6, 2025

Array API standard conformance tests for dpctl=0.21.0dev0=py310h93fe807_8 ran successfully.
Passed: 1115
Failed: 6
Skipped: 119

@coveralls
Copy link
Collaborator

coveralls commented Jun 6, 2025

Coverage Status

coverage: 84.708% (-0.3%) from 84.972%
when pulling 36ee0f2 on feature/searchsorted-based-isin
into 907a64e on master.

isin leverages kernel very similar to searchsorted, but after the search, the position is checked, and if the position is equal to the number of elements in the searched array, existence is considered false
@ndgrigorian ndgrigorian force-pushed the feature/searchsorted-based-isin branch from 1805102 to 5355fb8 Compare June 6, 2025 22:41
Copy link

github-actions bot commented Jun 6, 2025

Array API standard conformance tests for dpctl=0.21.0dev0=py310h93fe807_10 ran successfully.
Passed: 1114
Failed: 7
Skipped: 119


dep_evs = _manager.submitted_events
ht_ev, s_ev = _isin(
needles=x1,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the only case when the strided implementation (which assumes slower) will be used when x1 array is not contiguous (we sort test_elements array and no out keyword in isin function).
Would it make sense to flatten input array x and to pass order keyword there?

But, it makes sense also to keep strided implementation of _isin in case when it might be helpful in implementation of other set functions).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can experiment and see values of flattening vs. not flattening

but in general, this implementation is going to be changed quite a bit soon, I have some local changes waiting

permit scalar input for second argument, address some review comments, add docstring
@ndgrigorian
Copy link
Collaborator Author

@antonwolfy
implementation is updated and now permits Python scalars in second argument

tests still need to be added, but isin itself is ready for review

@ndgrigorian ndgrigorian marked this pull request as ready for review June 11, 2025 22:15
@ndgrigorian ndgrigorian requested a review from antonwolfy June 11, 2025 22:20
Copy link

Array API standard conformance tests for dpctl=0.21.0dev0=py310h93fe807_17 ran successfully.
Passed: 1115
Failed: 6
Skipped: 119

@@ -112,6 +112,7 @@ set(_reduction_sources
${CMAKE_CURRENT_SOURCE_DIR}/libtensor/source/reductions/sum.cpp
)
set(_sorting_sources
${CMAKE_CURRENT_SOURCE_DIR}/libtensor/source/sorting/isin.cpp
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem relating to sorting routine

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it uses common utilities with searchsorted (i.e., from rich_comparisons.hpp) which is why it lives there

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the code from rich_comparisons gets factored out, I can go ahead and move it elsewhere, I guess to _tensor_impl for now

input array.
test_elements (Union[usm_ndarray, bool, int, float, complex]):
elements against which to test each value of `x`.
Default: `None`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't be None I assume

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, copy-paste error

# copy into C-contiguous memory, because the array will be flattened
test_buf = dpt.empty_like(test_elements, dt, order="C")
dep_evs = _manager.submitted_events
ht_ev, ev = _copy_usm_ndarray_into_usm_ndarray(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This copy and the one at line 698 can be done independently if any.

x_buf = x

if not isinstance(test_elements, dpt.usm_ndarray):
test_buf = dpt.asarray(test_elements, dtype=dt, sycl_queue=exec_q)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to pass usm_type keyword?

test_buf = dpt.reshape(test_buf, -1)
test_buf = dpt.sort(test_buf)

dst = _empty_like_orderK(x_buf, dpt.bool, usm_type=res_usm_type)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to keep the same order like used by x? Wouldn't it be better to return C-contig array?

test_dt = _get_dtype(test_elements, sycl_dev)
if not _validate_dtype(test_dt):
raise ValueError("`test_elements` has unsupported dtype")

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to handle the corner case separately at some point?

    if isinstance(test_elements, dpt.usm_ndarray) and test_elements.size == 0:
        if invert:
            return dpt.ones_like(ar1, dtype=bool, usm_type=res_usm_type)
        else:
            return dpt.zeros_like(ar1, dtype=bool, usm_type=res_usm_type)

fnT get() const
{
using dpctl::tensor::kernels::isin_contig_impl;
using Compare = typename AscendingSorter<argTy>::type;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to have Compare templated here? Is there any use case possible when another one will be required to be used by isin kernel?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not strictly necessary, but was done to reduce code duplication—these sorters are defined in tensor/source.

I can look at the normal sort implementation to refresh myself on what was done there, but if that isn't sufficient, it may be preferable to template and pass the sorter here as opposed to duplicating in isin.hpp


void operator()(sycl::id<1> id) const
{
const Compare comp{};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const Compare comp{};
static constexpr Compare comp{};

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point!

test_buf = dpt.asarray(test_elements, dtype=dt, sycl_queue=exec_q)
elif test_dt != dt:
# copy into C-contiguous memory, because the array will be flattened
test_buf = dpt.empty_like(test_elements, dt, order="C")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test_buf = dpt.empty_like(test_elements, dt, order="C")
test_buf = dpt.empty_like(test_elements, dtype=dt, order="C")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants